package com.biz.primus.ms.geo.service;

import com.alibaba.fastjson.JSONObject;
import com.biz.primus.base.enums.CommonStatus;
import com.biz.primus.base.exception.BizSilentException;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.base.vo.PageVo;
import com.biz.primus.common.utils.BaiduMapUtil;
import com.biz.primus.common.utils.CopyUtils;
import com.biz.primus.common.vo.SpringPageVO;
import com.biz.primus.model.geo.enums.GeoLevel;
import com.biz.primus.model.geo.exception.GeoException;
import com.biz.primus.model.geo.vo.*;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.base.vo.SpringPageToSpringPageVO;
import com.biz.primus.ms.geo.dao.po.*;
import com.biz.primus.ms.geo.dao.redis.CityRedisDao;
import com.biz.primus.ms.geo.dao.redis.DistrictRedisDao;
import com.biz.primus.ms.geo.dao.redis.ProvinceRedisDao;
import com.biz.primus.ms.geo.dao.repository.CityRepository;
import com.biz.primus.ms.geo.dao.repository.DistrictRepository;
import com.biz.primus.ms.geo.dao.repository.GeoPushLogRepository;
import com.biz.primus.ms.geo.dao.repository.ProvinceRepository;
import com.biz.primus.ms.geo.dao.ro.CityRo;
import com.biz.primus.ms.geo.dao.ro.DistrictRo;
import com.biz.primus.ms.geo.dao.ro.ProvinceRo;
import com.biz.primus.ms.geo.stream.GeoUpdateSource;
import com.biz.primus.ms.geo.trans.*;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.biz.primus.ms.geo.trans.GeoTransformer.*;
import static com.google.common.collect.Lists.newArrayList;


/**
 * @author yanweijin
 * @date 2016/12/18
 */
@Slf4j
@Service
public class GeoService extends AbstractBaseService {

    private static final Integer DISTRICT_ID = 11;
    private static final Integer CITY_ID = 75;
    private static final Integer PROVENCE_ID = 32;
    private static final ProvinceRo2ProvinceResponseVo RO_2_PROVINCE_RESPONSE_VO = new ProvinceRo2ProvinceResponseVo();
    private static final CityRo2CityResponseVo RO_2_CITY_RESPONSE_VO = new CityRo2CityResponseVo();
    private static final DistrictRo2DistrictResponseVo RO_2_DISTRICT_RESPONSE_VO = new DistrictRo2DistrictResponseVo();
    private static final AreaRo2AreaResponseVo RO_2_AREA_RESPONSE_VO = new AreaRo2AreaResponseVo();

    private final ProvinceRedisDao provinceRedisDao;
    private final CityRedisDao cityRedisDao;
    private final DistrictRedisDao districtRedisDao;
    private final ProvinceRepository provinceRepository;
    private final CityRepository cityRepository;
    private final DistrictRepository districtRepository;
    private final GeoPushLogRepository geoPushLogRepository;
    private final GeoUpdateSource geoUpdateSource;

    @Autowired
    GeoService(ProvinceRedisDao provinceRedisDao,
               CityRedisDao cityRedisDao,
               DistrictRedisDao districtRedisDao,
               ProvinceRepository provinceRepository,
               DistrictRepository districtRepository,
               CityRepository cityRepository,
               GeoPushLogRepository geoPushLogRepository,
               GeoUpdateSource geoUpdateSource) {
        this.provinceRedisDao = provinceRedisDao;
        this.cityRedisDao = cityRedisDao;
        this.districtRedisDao = districtRedisDao;
        this.provinceRepository = provinceRepository;
        this.cityRepository = cityRepository;
        this.districtRepository = districtRepository;
        this.geoPushLogRepository = geoPushLogRepository;
        this.geoUpdateSource = geoUpdateSource;
    }

    public List<SimpleProvinceVo> buildPushProvince() {
        List<Province> provinces = provinceRepository.findByStatus(CommonStatus.ENABLE);
        return provinces.stream().map(po -> {
            SimpleProvinceVo vo = new SimpleProvinceVo();
            vo.setProvinceId(po.getId().longValue());
            vo.setProvinceName(po.getBaiduname());
            return vo;
        }).collect(Collectors.toList());
    }

    public List<SimpleCityVo> buildPushCity() {
        List<City> cities = cityRepository.findByStatus(CommonStatus.ENABLE);
        return cities.stream().map(po -> {
            SimpleCityVo vo = new SimpleCityVo();
            vo.setProvinceId(po.getProvinceId().longValue());
            vo.setCityId(po.getId().longValue());
            vo.setCityName(po.getBaiduname());
            return vo;
        }).collect(Collectors.toList());
    }

    public List<SimpleDistrictVo> buildPushDistrict() {
        List<District> cities = districtRepository.findByStatus(CommonStatus.ENABLE);
        return cities.stream().map(po -> {
            SimpleDistrictVo vo = new SimpleDistrictVo();
            vo.setProvinceId(po.getProvinceId().longValue());
            vo.setCityId(po.getCityId().longValue());
            vo.setDistrictId(po.getId().longValue());
            vo.setDistrictName(po.getBaiduname());
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 返回中国所有省的列表
     *
     * @return 所有省的 volist
     */
    @SuppressWarnings("unchecked")
    public List<AreaResponseVo> findAllProvince() {
        log.debug("find all province");
            throw new RuntimeException("跑熔断");
//            List<ProvinceRo> provinceRos = allGeoAreaCache.get(IArea.LEVEL_PROVINCE);
//            if (CollectionUtils.isNotEmpty(provinceRos)) {
//                return Lists.transform(provinceRos, RO_2_AREA_RESPONSE_VO);
//            }
//        try {
//        } catch (ExecutionException e) {
//            log.error("find all province error {}", e.getMessage(), e);
//        }
//        return null;
    }


    /**
     * 根据省id 获取该省所有市的 volist
     *
     * @param provinceId 省ID
     */
    @SuppressWarnings("unchecked")
    public List<AreaResponseVo> findCitiesByProvinceId(Integer provinceId) {
        List<AreaResponseVo> areaResponseVos = newArrayList();
        throw new RuntimeException("跑熔断");
//        try {
//            List<CityRo> citiesRos = geoChildrenAreaCache.get(new GeoLoadChildrenKey(provinceId, IArea.LEVEL_PROVINCE));
//            if (CollectionUtils.isNotEmpty(citiesRos)) {
//                return Lists.transform(citiesRos, RO_2_AREA_RESPONSE_VO);
//            }
//        } catch (Exception e) {
//            log.error("find cities by province id {} in guava cache error", provinceId, e);
//        }
//        return areaResponseVos;
    }

    /**
     * 记录推送日志
     */
    public void doPushLog(Integer size, GeoLevel level, Integer returnCode) {
        GeoPushLog log = new GeoPushLog();
        log.setId(idService.getNextId());
        log.setReturnCode(returnCode);
        log.setLevel(level);
        log.setSize(size);
        geoPushLogRepository.save(log);
    }

    /**
     * 根据市id获取该市所有的区
     *
     * @param cityId 市ID
     */
    @SuppressWarnings("unchecked")
    public List<AreaResponseVo> findDistrictsByCityId(Integer cityId) {
        List<AreaResponseVo> areaResponseVos = newArrayList();
//        try {
//            List<DistrictRo> districtRos = geoChildrenAreaCache.get(new GeoLoadChildrenKey(cityId, IArea.LEVEL_CITY));
//            if (CollectionUtils.isNotEmpty(districtRos)) {
//                return Lists.transform(districtRos, RO_2_AREA_RESPONSE_VO);
//            }
//        } catch (Exception e) {
//            log.error("find districts by city id {} in guava cache error", cityId, e);
//        }
//        return areaResponseVos;
        throw new RuntimeException("跑熔断");
    }

    /**
     * 查省的信息
     *
     * @param provinceId 省ID
     */
    public ProvinceResponseVo findProvince(Integer provinceId) {
        AssertUtils.notNull(provinceId, GeoException.PROVENCE_ID_IS_NULL);
        ProvinceRo provinceRo = provinceRedisDao.findOne(provinceId);
        AssertUtils.notNull(provinceRo, GeoException.PROVINCE_NOT_FOUND);
        return RO_2_PROVINCE_RESPONSE_VO.apply(provinceRo);
    }

    /**
     * 查市的信息
     *
     * @param cityId 市ID
     */
    public CityResponseVo findCity(Integer cityId) {
        AssertUtils.notNull(cityId, GeoException.CITY_ID_IS_NULL);
        CityRo cityRo = cityRedisDao.findOne(cityId);
        AssertUtils.notNull(cityRo, GeoException.CITY_NOT_FOUND);
        return RO_2_CITY_RESPONSE_VO.apply(cityRo);
    }

    /**
     * 查区县的信息
     *
     * @param districtId 区县ID
     */
    public DistrictResponseVo findDistrict(Integer districtId) {
        AssertUtils.notNull(districtId, GeoException.DISTRICT_ID_IS_NULL);
        DistrictRo districtRo = districtRedisDao.findOne(districtId);
        AssertUtils.notNull(districtRo, GeoException.DISTRICT_NOT_FOUND);
        return RO_2_DISTRICT_RESPONSE_VO.apply(districtRo);
    }

    /**
     * 根据经纬度 查位置信息
     *
     * @param reqVo 经纬度VO
     */
    public CityItemRespVo decodeLocation(LocationDecodeRequestVo reqVo) {
        log.debug("Start Decode location, latitude=[{}], longitude=[{}]", reqVo.getLatitude(), reqVo.getLongitude());
        Double lat = reqVo.getLatitude() != null ? reqVo.getLatitude().doubleValue() : Double.MIN_VALUE;
        Double lon = reqVo.getLongitude() != null ? reqVo.getLongitude().doubleValue() : Double.MIN_VALUE;
        BaiduDecodeRespVo decodeRespVo;
        if (Objects.equals(Double.MIN_VALUE, lat) && Objects.equals(Double.MIN_VALUE, lon)) {
            throw new BizSilentException("定位失败");
        } else {
            String decodeResp = BaiduMapUtil.getLocationDecode(lat, lon);
            log.debug("Decode location from baidu, infomation=[{}]", decodeResp);
            decodeRespVo = JSONObject.parseObject(decodeResp, BaiduDecodeRespVo.class);
        }
        if (decodeRespVo.getResult() == null
                || decodeRespVo.getResult().getAddressComponent() == null
                || StringUtils.isEmpty(decodeRespVo.getResult().getAddressComponent().getCity())
                || StringUtils.isEmpty(decodeRespVo.getResult().getAddressComponent().getProvince())) {
            throw new BizSilentException("定位失败");
        }
        String cityBaiduName = decodeRespVo.getResult().getAddressComponent().getCity();
        String provinceBaiduName = decodeRespVo.getResult().getAddressComponent().getProvince();
        CityRo cityRo = cityRedisDao.findCityByBaiduName(cityBaiduName);
        ProvinceRo provinceRo = provinceRedisDao.findProvinceByBaiduName(provinceBaiduName);
        AssertUtils.isTrue(Objects.nonNull(cityRo) && Objects.nonNull(provinceRo), GeoException.LOCATE_FAILED);
        return getCityItemVo(provinceRo, cityRo);
    }

    /**
     * 根据定位区域和城市百度NAME获取省市区信息
     *
     * @param cityName     市名称
     * @param districtName 区名称
     */
    public LocationAreaVo decodeLocation(String cityName, String districtName) {
        log.debug("根据定位区域和城市百度NAME获取省市区信息,城市名:{},区域名:{}", cityName, districtName);
        if (StringUtils.isNotEmpty(cityName) && StringUtils.isNotEmpty(districtName)) {
            CityRo city = cityRedisDao.findCityByBaiduName(cityName);
            if (city == null) {
                List<CityRo> allCitys = cityRedisDao.findAll();
                //1.匹配百度名称
                for (CityRo cityRo : allCitys) {
                    if (cityName.equals(cityRo.getBaiduname())) {
                        city = cityRo;
                        break;
                    }
                }
                //2.匹配名称
                if (city == null) {
                    for (CityRo cityRo : allCitys) {
                        if (cityName.equals(cityRo.getName())) {
                            city = cityRo;
                            break;
                        }
                    }
                }
                //3.模糊匹配
                if (city == null) {
                    for (CityRo cityRo : allCitys) {
                        if ((StringUtils.isNotEmpty(cityRo.getBaiduname()) && cityRo.getBaiduname().contains(cityName))
                                || (StringUtils.isNotEmpty(cityRo.getBaiduname()) && cityName.contains(cityRo.getBaiduname()))) {
                            city = cityRo;
                            break;
                        }
                    }
                }
            }
            if (city != null) {
                ProvinceRo province = provinceRedisDao.findOne(city.getProvinceId());
                List<DistrictRo> districts = districtRedisDao.findDistrictByCityId(city.getId());
                if (CollectionUtils.isNotEmpty(districts) && province != null) {
                    for (DistrictRo district : districts) {
                        if (district.getBaiduname().equals(districtName) || districtName.contains(district.getBaiduname())) {
                            return getLocationAreaVo(province, city, district);
                        }
                    }
                }
            }
        }
        DistrictRo district = districtRedisDao.findOne(DISTRICT_ID);
        CityRo city = cityRedisDao.findOne(CITY_ID);
        ProvinceRo province = provinceRedisDao.findOne(PROVENCE_ID);
        return getLocationAreaVo(province, city, district);
    }

    /**
     * 通过百度名称来查省份信息
     *
     * @param baiduName 百度名称
     */
    public Optional<ProvinceResponseVo> findProvinceByBaiduName(String baiduName) {
        log.debug("find province by baiduname : {}", baiduName);
        if (StringUtils.isBlank(baiduName)) {
            return null;
        }
        ProvinceRo provinceRo = provinceRedisDao.findProvinceByBaiduName(baiduName);
        ProvinceResponseVo pro = RO_2_PROVINCE_RESPONSE_VO.apply(provinceRo);
        return Optional.ofNullable(pro);
    }

    /**
     * 通过百度名称来查市信息
     *
     * @param baiduName 百度名称
     */
    public Optional<CityResponseVo> findCityByBadiuName(String baiduName) {
        log.debug("find city by baiduname : {}", baiduName);
        if (StringUtils.isBlank(baiduName)) {
            return null;
        }
        CityRo cityRo = cityRedisDao.findCityByBaiduName(baiduName);
        CityResponseVo cityResponseVo = RO_2_CITY_RESPONSE_VO.apply(cityRo);
        return Optional.ofNullable(cityResponseVo);
    }

    /**
     * 通过百度名称来查区信息
     *
     * @param baiduName 百度名称
     */
    public Optional<DistrictResponseVo> findDistrictByBaiduName(String baiduName) {
        log.debug("find district by baiduname : {}", baiduName);
        if (StringUtils.isBlank(baiduName)) {
            return null;
        }
        DistrictRo districtRo = districtRedisDao.findDistrictByBaiduName(baiduName);
        log.debug("current district is [{}]", districtRo.toString());
        DistrictResponseVo di = RO_2_DISTRICT_RESPONSE_VO.apply(districtRo);
        return Optional.ofNullable(di);
    }


    /**
     * 获取城市及其市区的vo集合
     */
    @SuppressWarnings("unchecked")
    public List<ProvinceCitiesItemResponseVo> getProvinceCities() {
        log.debug("start find provinces cities ...");
        List<ProvinceRo> provinceRos;
        try {
            provinceRos = allGeoAreaCache.get(IArea.LEVEL_PROVINCE);
        } catch (Exception e) {
            log.error("get province ros from cache error.", e);
            return new ArrayList<>();
        }
        List<CityRo> allCities = newArrayList();
        if (CollectionUtils.isNotEmpty(provinceRos)) {
            for (ProvinceRo provinceRo : provinceRos) {
                if (provinceRo != null) {
                    List<CityRo> cityRos;
                    try {
                        cityRos = geoChildrenAreaCache.get(new GeoLoadChildrenKey(provinceRo.getId(), IArea
                                .LEVEL_PROVINCE));
                    } catch (Exception e) {
                        log.error("get city ros from cache error.", e);
                        return new ArrayList<>();
                    }
                    allCities.addAll(cityRos);
                }
            }
        }
        Collections.sort(allCities);
        Map<String, List<CityItemRespVo>> cityItemMap = Maps.newLinkedHashMap();
        for (CityRo cityRo : allCities) {
            List<CityItemRespVo> cityItemVos = cityItemMap.get(cityRo.getPrefix());
            if (cityItemVos == null) {
                cityItemVos = newArrayList();
            }
            cityItemVos.add(this.getCityItemVo(provinceRedisDao.findOne(cityRo.getProvinceId()), cityRo));
            cityItemMap.put(cityRo.getPrefix(), cityItemVos);
        }
        List<ProvinceCitiesItemResponseVo> itemVos = newArrayList();
        for (Map.Entry<String, List<CityItemRespVo>> entry : cityItemMap.entrySet()) {
            ProvinceCitiesItemResponseVo itemVo = new ProvinceCitiesItemResponseVo();
            itemVo.setPrefix(entry.getKey());
            itemVo.setCities(entry.getValue());
            itemVos.add(itemVo);
        }
        return itemVos;
    }


    /**
     * 获取全部的市及市所在的省
     */
    public List<AreaResponseVo> findAllCitiesWithProvinceId() {
        log.debug("find all cities");
        List<CityRo> cityRos = cityRedisDao.findAll();
        return Lists.transform(cityRos, new CityRo2AreaResponseVo());
    }

    /**
     * 获取全部的市
     */
    public List<AreaResponseVo> findAllCities() {
        log.debug("find all cities");
        List<CityRo> cityRos = cityRedisDao.findAll();
        return Lists.transform(cityRos, RO_2_AREA_RESPONSE_VO);
    }

    /**
     * 获取全部的区
     */
    public List<AreaResponseVo> findAllDistricts() {
        List<DistrictRo> districtRos = districtRedisDao.findAll();
        return Lists.transform(districtRos, RO_2_AREA_RESPONSE_VO);
    }

    /**
     * 构建城市详情相应vo
     *
     * @param provinceRo 省RO
     * @param cityRo     市RO
     * @return 市详情信息
     */
    private CityItemRespVo getCityItemVo(ProvinceRo provinceRo, CityRo cityRo) {
        CityItemRespVo cityItemVo = new CityItemRespVo();
        cityItemVo.setCityId(cityRo.getId().longValue());
        cityItemVo.setProvinceId(provinceRo.getId().longValue());
        cityItemVo.setProvinceName(provinceRo.getName());
        cityItemVo.setCityName(cityRo.getName());
        cityItemVo.setPrefix(cityRo.getPrefix());
        return cityItemVo;
    }


    /**
     * GEO同步方法
     */
    public void syncMysql2Redis() {
        log.info("开始同步GEO信息到Redis.");
        StopWatch stopWatch = new StopWatch("GEO同步");
        stopWatch.start("同步省");
        List<Province> provinces = provinceRepository.findAll();
        provinces = provinces.stream().filter(po -> CommonStatus.DISABLE == po.getStatus()).collect(Collectors.toList());
        List<ProvinceRo> provinceRos = Lists.transform(provinces, PROVINCE_TRANSFORMER);
        provinceRedisDao.save(provinceRos);
        log.info("-----------同步省{}------", provinceRos.size());
        stopWatch.stop();
        stopWatch.start("同步市");
        int page = 0;
        int size = 1000;
        Page<City> cityPage;
        PageRequest pa;
        do {
            pa = new PageRequest(page++, size);
            cityPage = cityRepository.findAll(pa);
            List<City> cities = cityPage.getContent();
            cities = cities.stream().filter(po -> CommonStatus.DISABLE == po.getStatus()).collect(Collectors.toList());
            List<CityRo> cityRos = Lists.transform(cities, CITY_TRANSFORMER);
            cityRedisDao.save(cityRos);
            log.info("-----------同步市第{}页,当前页{}条数据------.", page, cityRos.size());
        } while (cityPage.hasNext());
        stopWatch.stop();
        stopWatch.start("同步区");
        page = 0;
        Page<District> districtPage;
        do {
            pa = new PageRequest(page++, size);
            districtPage = districtRepository.findAll(pa);
            List<District> districts = districtPage.getContent();
            districts = districts.stream().filter(po -> CommonStatus.DISABLE == po.getStatus()).collect(Collectors.toList());
            List<DistrictRo> districtRos = Lists.transform(districts, DISTRICT_TRANSFORMER);
            districtRedisDao.save(districtRos);
            log.info("-----------同步区县第{}页,当前页{}条数据------.", page, districtRos.size());
        } while (cityPage.hasNext());
        stopWatch.stop();
        stopWatch.prettyPrint();
        log.info("同步GEO信息到Redis完成.");
    }


    /**
     * 根据市名称模糊查询
     */
    public List<CityItemVo> listByNameOfLike(String cityName) {
        AssertUtils.hasLength(cityName, GeoException.CITY_NAME_IS_NULL);
        List<City> list = cityRepository.findByNameOfLike(cityName);
        return Lists.transform(list, new City2CityItemVo(provinceRepository));
    }

    /**
     * 校验区域有效性
     *
     * @param lon         经度
     * @param lat         纬度
     * @param provinceIds 省id
     */
    public boolean checkedAreaEffective(BigDecimal lon, BigDecimal lat, Long... provinceIds) {
        if (lon == null || lat == null || ArrayUtils.isEmpty(provinceIds)) {
            return false;
        }
        Double latitude = lat.doubleValue();
        Double longitude = lon.doubleValue();
        BaiduDecodeRespVo decodeRespVo;
        String decodeResp = BaiduMapUtil.getLocationDecode(latitude, longitude);
        log.info("lat={}, lon={},百度地图解析经纬度返回结果={}", lat, lon, decodeResp);
        decodeRespVo = JSONObject.parseObject(decodeResp, BaiduDecodeRespVo.class);
        BaiduDecodeResultVo decodeResultVo = decodeRespVo.getResult();
        AssertUtils.isTrue(decodeResultVo != null
                        && decodeResultVo.getAddressComponent() != null
                        && decodeResultVo.getAddressComponent().getCity() != null
                        && decodeResultVo.getAddressComponent().getProvince() != null,
                GeoException.LOCATE_FAILED);
        String provinceName = decodeResultVo.getAddressComponent().getProvince();
        ProvinceRo provinceRo = provinceRedisDao.findProvinceByBaiduName(provinceName);
        for (Long provinceId : provinceIds) {
            if (Objects.equals(provinceId.intValue(), provinceRo.getId())) {
                return true;
            }
        }
        return false;
    }


    /**
     * 查全省
     */
    @SuppressWarnings("unchecked")
    public List<ProvincesItemResponseVo> getProvinces() {
        log.debug("start get provinces ...");
        List<ProvinceRo> provinceRos;
        try {
            provinceRos = allGeoAreaCache.get(IArea.LEVEL_PROVINCE);
        } catch (Exception e) {
            log.error("get province ros from cache error.", e);
            provinceRos = newArrayList();
        }
        Map<String, List<ProvinceItemResponseVo>> provinceItemMap = Maps.newLinkedHashMap();
        for (ProvinceRo provinceRo : provinceRos) {
            List<ProvinceItemResponseVo> provinceItemVos = provinceItemMap.get(provinceRo.getPrefix());
            if (provinceItemVos == null) {
                provinceItemVos = newArrayList();
            }
            String name = provinceRo.getName();
            provinceRo.setPrefix(getFirstLetter(name));
            provinceItemVos.add(this.getProvinceItemVo(provinceRo));
            provinceItemMap.put(provinceRo.getPrefix(), provinceItemVos);
        }
        List<ProvincesItemResponseVo> itemVos = newArrayList();
        for (Map.Entry<String, List<ProvinceItemResponseVo>> entry : provinceItemMap.entrySet()) {
            ProvincesItemResponseVo itemVo = new ProvincesItemResponseVo();
            itemVo.setPrefix(entry.getKey());
            itemVo.setProvinces((entry.getValue()));
            itemVos.add(itemVo);
        }
        return itemVos;
    }

    /**
     * 更新修改省信息
     *
     * @param reqVo 更新详情信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateProvince(GeoUpdateReqVo reqVo) {
        log.debug("updateProvince:{}", reqVo);
        Province province;
        Long id = reqVo.getId();
        AssertUtils.notNull(id, GeoException.PROVENCE_ID_IS_NULL);
        province = provinceRepository.findOne(id.intValue());
        if (province == null) {
            province = new Province();
            province.setId(id.intValue());
        }
        CopyUtils.copyProperties(reqVo, province);
        provinceRepository.save(province);
        ProvinceRo provinceRo = PROVINCE_TRANSFORMER.apply(province);
        SimpleProvinceVo simple = new SimpleProvinceVo();
        simple.setProvinceId(province.getId().longValue());
        simple.setProvinceName(province.getBaiduname());
        delayer.executeAfterTransactionCommit(() -> {
            provinceRedisDao.save(provinceRo);
            this.pushProvince(simple);
        });
    }

    /**
     * 更新市信息
     *
     * @param reqVo 更新详情信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateCity(GeoUpdateReqVo reqVo) {
        City city;
        Long id = reqVo.getId();
        Long parentId = reqVo.getParentId();
        AssertUtils.notNull(id, GeoException.CITY_ID_IS_NULL);
        AssertUtils.notNull(parentId, GeoException.PROVENCE_ID_IS_NULL);
        city = cityRepository.findOne(id.intValue());
        if (city == null) {
            city = new City();
            city.setId(id.intValue());
        }
        CopyUtils.copyProperties(reqVo, city);
        city.setProvinceId(parentId.intValue());
        cityRepository.save(city);
        CityRo cityRo = CITY_TRANSFORMER.apply(city);
        SimpleCityVo simple = new SimpleCityVo();
        simple.setCityId(city.getId().longValue());
        simple.setCityName(city.getBaiduname());
        simple.setProvinceId(city.getProvinceId().longValue());
        delayer.executeAfterTransactionCommit(() -> {
            cityRedisDao.save(cityRo);
            this.pushCity(simple);
        });
    }

    /**
     * 更新区信息
     *
     * @param reqVo 更新详情信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDistrict(GeoUpdateReqVo reqVo) {
        District district;
        Long id = reqVo.getId();
        Long parentId = reqVo.getParentId();
        AssertUtils.notNull(id, GeoException.DISTRICT_ID_IS_NULL);
        AssertUtils.notNull(parentId, GeoException.CITY_ID_IS_NULL);
        district = districtRepository.findOne(id.intValue());
        if (district == null) {
            district = new District();
            district.setId(id.intValue());
        }
        City city = cityRepository.findOne(parentId.intValue());
        AssertUtils.notNull(city, GeoException.CITY_NOT_FOUND);
        CopyUtils.copyProperties(reqVo, district);
        district.setProvinceId(city.getProvinceId());
        district.setCityId(parentId.intValue());
        districtRepository.save(district);
        DistrictRo districtRo = DISTRICT_TRANSFORMER.apply(district);
        SimpleDistrictVo simple = new SimpleDistrictVo();
        simple.setDistrictId(district.getId().longValue());
        simple.setDistrictName(district.getName());
        simple.setCityId(parentId);
        simple.setProvinceId(city.getProvinceId().longValue());
        delayer.executeAfterTransactionCommit(() -> {
            districtRedisDao.save(districtRo);
            this.pushDistrict(simple);
        });
    }

    public void pushAll(){

    }
    private void pushProvince(SimpleProvinceVo provinceVo) {
        geoUpdateSource.pushProvince().send(MessageBuilder.withPayload(provinceVo).build());
    }

    private void pushCity(SimpleCityVo cityVo) {
        geoUpdateSource.pushCity().send(MessageBuilder.withPayload(cityVo).build());
    }

    private void pushDistrict(SimpleDistrictVo districtVo) {
        geoUpdateSource.pushDistrict().send(MessageBuilder.withPayload(districtVo).build());
    }

    /**
     * 分页获取全部的省
     *
     * @param reqVo 分页对象
     */
    public SpringPageVO<ProvinceListRespVo> listProvince(PageVo reqVo) {
        PageRequest pageRequest = new PageRequest(reqVo.getPage() - 1, reqVo.getSize());
        Page<Province> all = provinceRepository.findAll(pageRequest);
        return SpringPageToSpringPageVO.of(all, province -> {
            ProvinceListRespVo resp = new ProvinceListRespVo();
            if (province != null) {
                CopyUtils.copyProperties(province, resp);
                resp.setId(String.valueOf(province.getId()));
            }
            return resp;
        });
    }

    /**
     * 分页获取省下面的市
     *
     * @param provinceId 省ID
     * @param reqVo      分页对象VO
     */
    public SpringPageVO<CityListRespVo> listCity(Long provinceId, PageVo reqVo) {
        PageRequest pageRequest = new PageRequest(reqVo.getPage() - 1, reqVo.getSize());
        Page<City> all = cityRepository.findCitiesByProvinceId(provinceId.intValue(), pageRequest);
        Province one = provinceRepository.findOne(provinceId.intValue());
        if (one == null) {
            return new SpringPageVO<>();
        }
        String provinceName = one.getBaiduname();
        return SpringPageToSpringPageVO.of(all, city -> {
            CityListRespVo resp = new CityListRespVo();
            if (city != null) {
                CopyUtils.copyProperties(city, resp);
                resp.setId(String.valueOf(city.getId()));
                resp.setProvinceName(provinceName);
            }
            return resp;
        });
    }

    /**
     * 分页获取市下面的区
     *
     * @param cityId 城市ID
     * @param reqVo  分页对象VO
     */
    public SpringPageVO<DistrictListRespVo> listDistrict(Long cityId, PageVo reqVo) {
        PageRequest pageRequest = new PageRequest(reqVo.getPage() - 1, reqVo.getSize());
        Page<District> all = districtRepository.findDistrictsByCityId(cityId.intValue(), pageRequest);
        City one = cityRepository.findOne(cityId.intValue());
        if (one == null) {
            return new SpringPageVO<>();
        }
        String cityName = one.getBaiduname();
        return SpringPageToSpringPageVO.of(all, city -> {
            DistrictListRespVo resp = new DistrictListRespVo();
            if (city != null) {
                CopyUtils.copyProperties(city, resp);
                resp.setId(String.valueOf(city.getId()));
                resp.setCityName(cityName);
            }
            return resp;
        });
    }

    /**
     * 根据ID查省
     *
     * @param provinceId 省ID
     */
    public ProvinceDetailRespVo findProvinceById(Long provinceId) {
        if (provinceId == null) {
            return new ProvinceDetailRespVo();
        }
        Province one = provinceRepository.findOne(provinceId.intValue());
        AssertUtils.notNull(one, GeoException.PROVINCE_NOT_FOUND);
        ProvinceDetailRespVo respVo = new ProvinceDetailRespVo();
        CopyUtils.copyProperties(one, respVo);
        respVo.setId(one.getId().longValue());
        return respVo;
    }

    /**
     * 根据Id查市
     *
     * @param cityId 市Id
     */
    public CityDetailRespVo findCityById(Long cityId) {
        if (cityId == null) {
            return new CityDetailRespVo();
        }
        City one = cityRepository.findOne(cityId.intValue());
        AssertUtils.notNull(one, GeoException.CITY_NOT_FOUND);
        CityDetailRespVo respVo = new CityDetailRespVo();
        CopyUtils.copyProperties(one, respVo);
        Integer provinceId = one.getProvinceId();
        respVo.setProvinceId(String.valueOf(provinceId));
        respVo.setId(String.valueOf(one.getId()));
        return respVo;
    }

    /**
     * 根据id查区县
     *
     * @param districtId 区县ID
     */
    public DistrictDetailRespVo findDistrictById(Long districtId) {
        if (districtId == null) {
            return new DistrictDetailRespVo();
        }
        District one = districtRepository.findOne(districtId.intValue());
        AssertUtils.notNull(one, GeoException.DISTRICT_NOT_FOUND);
        DistrictDetailRespVo respVo = new DistrictDetailRespVo();
        CopyUtils.copyProperties(one, respVo);
        Integer cityId = one.getCityId();
        respVo.setCityId(String.valueOf(cityId));
        respVo.setId(String.valueOf(one.getId()));
        return respVo;
    }

    /**
     * 获取首字母
     */
    private String getFirstLetter(String chineseLanguage) {
        char[] clChars = chineseLanguage.trim().toCharArray();
        String hanyupinyin = "";
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        // 输出拼音全部大写
        defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
        // 不带声调
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        try {
            String str = String.valueOf(clChars[0]);
            // 如果字符是中文,则将中文转为汉语拼音,并取第一个字母
            if (str.matches("[\u4e00-\u9fa5]+")) {
                hanyupinyin = PinyinHelper.toHanyuPinyinStringArray(clChars[0], defaultFormat)[0].substring(0, 1);
                // 如果字符是数字,取数字
            } else if (str.matches("[0-9]+")) {
                hanyupinyin += clChars[0];
                // 如果字符是字母,取字母
            } else if (str.matches("[a-zA-Z]+")) {
                hanyupinyin += clChars[0];
                // 否则不转换
            } else {

            }
        } catch (BadHanyuPinyinOutputFormatCombination e) {
            e.printStackTrace();
        }
        return hanyupinyin;
    }

    /**
     * 构建省详情信息
     *
     * @param provinceRo 省RO
     */
    private ProvinceItemResponseVo getProvinceItemVo(ProvinceRo provinceRo) {
        ProvinceItemResponseVo provinceItemVo = new ProvinceItemResponseVo();
        provinceItemVo.setProvinceId(provinceRo.getId().longValue());
        provinceItemVo.setProvinceName(provinceRo.getName());
        provinceItemVo.setPrefix(provinceRo.getPrefix());

        return provinceItemVo;
    }

    /**
     * 构建区详情VO
     *
     * @param provinceRo 省RO
     * @param cityRo     市RO
     * @param districtRo 区RO
     */
    private LocationAreaVo getLocationAreaVo(ProvinceRo provinceRo, CityRo cityRo, DistrictRo districtRo) {
        LocationAreaVo locationAreaVo = new LocationAreaVo();
        locationAreaVo.setDistrictId(districtRo.getId().longValue());
        locationAreaVo.setDistrictName(districtRo.getBaiduname());
        locationAreaVo.setCityId(cityRo.getId().longValue());
        locationAreaVo.setCityName(cityRo.getBaiduname());
        locationAreaVo.setProvinceId(provinceRo.getId().longValue());
        locationAreaVo.setProvinceName(provinceRo.getBaiduname());
        return locationAreaVo;
    }

    /**
     * 加载所有的省市区县到guava缓存
     */
    private LoadingCache<Integer, List> allGeoAreaCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(1000).build(new CacheLoader<Integer, List>() {
        @Override
        public List load(Integer areaLevel) throws Exception {
            log.info("load area info to guava cache by area level {}", areaLevel);
            switch (areaLevel) {
                case IArea.LEVEL_PROVINCE:
                    List<ProvinceRo> provinceRos = provinceRedisDao.findAll();
                    return provinceRos == null ? newArrayList() : provinceRos;
                case IArea.LEVEL_CITY:
                    List<CityRo> cityRos = cityRedisDao.findAll();
                    return cityRos == null ? newArrayList() : cityRos;
                case IArea.LEVEL_DISTRICT:
                    List<DistrictRo> districtRos = districtRedisDao.findAll();
                    return districtRos == null ? newArrayList() : districtRos;
                default:
                    return newArrayList();
            }
        }
    });
    /**
     * 加载省市区县的下级数据到guava缓存
     */
    private LoadingCache<GeoLoadChildrenKey, List> geoChildrenAreaCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(1000).build(new CacheLoader<GeoLoadChildrenKey, List>() {
        @Override
        public List load(GeoLoadChildrenKey geoLoadChildrenKey) throws Exception {
            log.info("load children area info to guava cache by key {}", geoLoadChildrenKey);
            switch (geoLoadChildrenKey.getAreaLevel()) {
                case IArea.LEVEL_PROVINCE:
                    List<CityRo> cityRos = cityRedisDao.findCitiesByProvinceId(geoLoadChildrenKey.id);
                    return cityRos == null ? newArrayList() : cityRos;
                case IArea.LEVEL_CITY:
                    List<DistrictRo> districtRos = districtRedisDao.findDistrictByCityId(geoLoadChildrenKey.id);
                    return districtRos == null ? newArrayList() : districtRos;
                default:
                    return newArrayList();
            }
        }
    });

    /**
     * 通过guava级联获取省市区的key
     */
    private class GeoLoadChildrenKey {
        private Integer id;
        private Integer areaLevel;

        public GeoLoadChildrenKey(Integer id, Integer areaLevel) {
            this.id = id;
            this.areaLevel = areaLevel;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Integer getAreaLevel() {
            return areaLevel;
        }

        public void setAreaLevel(Integer areaLevel) {
            this.areaLevel = areaLevel;
        }
    }

}
